#include "tcp_connection.hh"
#include <iostream>

using namespace std;

size_t TCPConnection::remaining_outbound_capacity() const { return _sender.stream_in().remaining_capacity(); }

size_t TCPConnection::bytes_in_flight() const { return _sender.bytes_in_flight(); }

size_t TCPConnection::unassembled_bytes() const { return _receiver.unassembled_bytes(); }

size_t TCPConnection::time_since_last_segment_received() const { return _time_since_last_segment_received; }

bool TCPConnection::active() const { return _active; }

void TCPConnection::segment_received(const TCPSegment &seg) {
    if (!_active)
        return;
    _time_since_last_segment_received = 0;

    // STATE: LISTEN => SYN_RECV
    // receive SYN / send SYN + ACK
    if (!_receiver.ackno().has_value() && _sender.next_seqno_absolute() == 0) {
        if (!seg.header().syn)
            return;
        _receiver.segment_received(seg);
        connect();
        return;
    }

    // STATE: SYN_SENT => ESTABLISED
    // receive SYN + ACK / send ACK
    if (!_receiver.ackno().has_value() && _sender.next_seqno_absolute() &&
        _sender.bytes_in_flight() == _sender.next_seqno_absolute()) {
//    if (!_receiver.ackno().has_value() && _sender.next_seqno_absolute() > 0 &&
//        _sender.next_seqno_absolute() == _sender.bytes_in_flight()) {
        if (seg.payload().size())
            return;
        if (!seg.header().ack && seg.header().syn) {
            _receiver.segment_received(seg);
            _sender.send_empty_segment();
            return;
        }
        if (seg.header().rst) {
            // now we haven't build the connection with the server,
            // so don't care about the server, don't need to inform it
            _receiver.stream_out().set_error();
            _sender.stream_in().set_error();
            _active = false;
            return;
        }
    }
    // give the seg to receiver
    _receiver.segment_received(seg);
    // tell the sender ackno and window_size
    if (seg.header().ack)
        _sender.ack_received(seg.header().ackno, seg.header().win);

    if (seg.header().rst) {
        _sender.send_empty_segment();
        unclean_shutdown();
        return;
    }

    // send at least one segment to reply

    // make sure there is at least one segment
    if (_sender.segments_out().empty() && seg.length_in_sequence_space())
        _sender.send_empty_segment();

    // send segments
    send_seg();
}

size_t TCPConnection::write(const string &data) {
    size_t write_size = _sender.stream_in().write(data);
    _sender.fill_window();
    send_seg();
    return write_size;
}

//! \param[in] ms_since_last_tick number of milliseconds since the last call to this method
void TCPConnection::tick(const size_t ms_since_last_tick) {
    if (!_active)
        return;
    _time_since_last_segment_received += ms_since_last_tick;
    // tell the sender about the passage of time
    _sender.tick(ms_since_last_tick);
    if (_sender.consecutive_retransmissions() > TCPConfig::MAX_RETX_ATTEMPTS)
        unclean_shutdown();

    send_seg();
}

void TCPConnection::end_input_stream() {
    _sender.stream_in().end_input();
    _sender.fill_window();
    send_seg();
}

void TCPConnection::connect() {
    // send syn
    _sender.fill_window();
    send_seg();
}

TCPConnection::~TCPConnection() {
    try {
        if (_active) {
            cerr << "Warning: Unclean shutdown of TCPConnection\n";
            _sender.send_empty_segment();
            unclean_shutdown();
        }
    } catch (const exception &e) {
        std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl;
    }
}

void TCPConnection::send_seg() {
    TCPSegment seg;
    while (!_sender.segments_out().empty()) {
        seg = _sender.segments_out().front();
        _sender.segments_out().pop();
        if (_receiver.ackno().has_value()) {
            seg.header().ack = true;
            seg.header().ackno = _receiver.ackno().value();
            seg.header().win = _receiver.window_size();
        }
        _segments_out.push(seg);
    }
    clean_shutdown();
}

void TCPConnection::unclean_shutdown() {
    // When this being called, _sender.stream_out() should not be empty.
    _receiver.stream_out().set_error();
    _sender.stream_in().set_error();
    _active = false;
    TCPSegment seg = _sender.segments_out().front();
    _sender.segments_out().pop();
    if (_receiver.ackno().has_value()) {
        seg.header().ack = true;
        seg.header().ackno = _receiver.ackno().value();
        seg.header().win = _receiver.window_size();
    }
    seg.header().rst = true;
    _segments_out.push(seg);
}

void TCPConnection::clean_shutdown() {
    // receiver input_ended is enough for application may never get the data off
    // the sender must eof to ensure all the data have been sent
    if (_receiver.stream_out().input_ended()) {
        if (!_sender.stream_in().eof())
            _linger_after_streams_finish = false;
        else if (_sender.bytes_in_flight() == 0) {
            // passive close or active close(timeout)
            if (!_linger_after_streams_finish || time_since_last_segment_received() >= 10 * _cfg.rt_timeout) {
                _active = false;
            }
        }
    }
}